{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7fPc-KWUi2Xd"
      },
      "outputs": [],
      "source": [
        "# Copyright 2021 Google LLC\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eoXf8TfQoVth"
      },
      "source": [
        "<table align=\"left\">\n",
        "\n",
        "  <td>\n",
        "    <a href=\"https://colab.research.google.com/github/GoogleCloudPlatform/ai-platform-samples/blob/master/ai-platform-unified/notebooks/official/vizier/gapic-vizier-multi-objective-optimization.ipynb\">\n",
        "      <img src=\"https://cloud.google.com/ml-engine/images/colab-logo-32px.png\" alt=\"Colab logo\"> Run in Colab\n",
        "    </a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://github.com/GoogleCloudPlatform/ai-platform-samples/blob/master/ai-platform-unified/notebooks/official/vizier/gapic-vizier-multi-objective-optimization.ipynb\">\n",
        "      <img src=\"https://cloud.google.com/ml-engine/images/github-logo-32px.png\" alt=\"GitHub logo\">\n",
        "      View on GitHub\n",
        "    </a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AksIKBzZ-nre"
      },
      "source": [
        "# Optimizing multiple objectives\n",
        "\n",
        "This tutorial demonstrates [Vertex Vizier](https://cloud.google.com/vertex-ai/docs/vizier/overview) multi-objective optimization. Multi-objective optimization is concerned with mathematical optimization problems involving more than one objective function to be optimized simultaneously\n",
        "\n",
        "### Objective\n",
        "\n",
        "The goal is to __`minimize`__ the objective metric:\n",
        "   ```\n",
        "   y1 = r*sin(theta)\n",
        "   ```\n",
        "\n",
        "and simultaneously __`maximize`__ the objective metric:\n",
        "   ```\n",
        "   y2 = r*cos(theta)\n",
        "   ```\n",
        "\n",
        "that you will evaluate over the parameter space:\n",
        "\n",
        "   - __`r`__ in [0,1],\n",
        "\n",
        "   - __`theta`__ in [0, pi/2]\n",
        "\n",
        "### Costs\n",
        "\n",
        "This tutorial uses billable components of Google Cloud:\n",
        "\n",
        "* Vertex AI\n",
        "\n",
        "Learn about [Vertex AI\n",
        "pricing](https://cloud.google.com/vertex-ai/pricing) and use the [Pricing\n",
        "Calculator](https://cloud.google.com/products/calculator/)\n",
        "to generate a cost estimate based on your projected usage.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iMHz63rPbq6P"
      },
      "source": [
        "#### Install Vertex AI library\n",
        "\n",
        "Download and install Vertex AI library."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b6f3dc43494b"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "\n",
        "# The Google Cloud Notebook product has specific requirements\n",
        "IS_GOOGLE_CLOUD_NOTEBOOK = os.path.exists(\"/opt/deeplearning/metadata/env_version\")\n",
        "\n",
        "# Google Cloud Notebook requires dependencies to be installed with '--user'\n",
        "USER_FLAG = \"\"\n",
        "if IS_GOOGLE_CLOUD_NOTEBOOK:\n",
        "    USER_FLAG = \"--user\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "g7WlujNxbq6Q"
      },
      "outputs": [],
      "source": [
        "! pip install {USER_FLAG} --upgrade google-cloud-aiplatform"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "O8AIwN0abq6U"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "\n",
        "if not os.getenv(\"IS_TESTING\"):\n",
        "    # Restart the kernel after pip installs\n",
        "    import IPython\n",
        "\n",
        "    app = IPython.Application.instance()\n",
        "    app.kernel.do_shutdown(True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Xc9LnICki2Xp"
      },
      "source": [
        "### Set up your Google Cloud project\n",
        "\n",
        "**The following steps are required, regardless of your notebook environment.**\n",
        "\n",
        "1. [Select or create a Google Cloud project.](https://console.cloud.google.com/cloud-resource-manager)\n",
        "\n",
        "2. [Make sure that billing is enabled for your project.](https://cloud.google.com/billing/docs/how-to/modify-project)\n",
        "\n",
        "3. [Enable the Vertex AI APIs](https://console.cloud.google.com/flows/enableapi?apiid=aiplatform.googleapis.com)\n",
        "\n",
        "4. Enter your project ID in the cell below. Then run the  cell to make sure the\n",
        "right project is used for all the commands in this notebook.\n",
        "\n",
        "**Note**: Jupyter runs lines prefixed with `!` as shell commands, and it interpolates Python variables prefixed with `$` into these commands."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h0SMyUsC-mzi"
      },
      "source": [
        "### Authenticate your Google Cloud account\n",
        "\n",
        "**If you are using [Google Cloud Notebooks](https://cloud.google.com/ai-platform/notebooks/docs/)**, your environment is already\n",
        "authenticated. Skip these steps."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iTQY9g4mRo6r"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "import sys\n",
        "\n",
        "# If you are running this notebook in Colab, run this cell and follow the\n",
        "# instructions to authenticate your GCP account. This provides access to your\n",
        "# Cloud Storage bucket and lets you submit training jobs and prediction\n",
        "# requests.\n",
        "\n",
        "# If on Google Cloud Notebooks, then don't execute this code\n",
        "if not os.path.exists(\"/opt/deeplearning/metadata/env_version\"):\n",
        "    if \"google.colab\" in sys.modules:\n",
        "        from google.colab import auth as google_auth\n",
        "\n",
        "        google_auth.authenticate_user()\n",
        "\n",
        "    # If you are running this notebook locally, replace the string below with the\n",
        "    # path to your service account key and run this cell to authenticate your GCP\n",
        "    # account.\n",
        "    elif not os.getenv(\"IS_TESTING\"):\n",
        "        %env GOOGLE_APPLICATION_CREDENTIALS ''"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Dax2zrpTi2Xy"
      },
      "source": [
        "### Import libraries and define constants"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xD60d6Q0i2X0"
      },
      "outputs": [],
      "source": [
        "import datetime\n",
        "import json\n",
        "\n",
        "from google.cloud import aiplatform_v1beta1"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CWuu4wmki2X3"
      },
      "source": [
        "## Tutorial\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KyEjqIdnad0w"
      },
      "source": [
        "This section defines some parameters and util methods to call Vertex Vizier APIs. Please fill in the following information to get started."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8HCgeF8had77"
      },
      "outputs": [],
      "source": [
        "# Fill in your project ID and region\n",
        "REGION = \"[region]\"  # @param {type:\"string\"}\n",
        "PROJECT_ID = \"[your-project-id]\"  # @param {type:\"string\"}\n",
        "\n",
        "# These will be automatically filled in.\n",
        "STUDY_DISPLAY_NAME = \"{}_study_{}\".format(\n",
        "    PROJECT_ID.replace(\"-\", \"\"), datetime.datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n",
        ")  # @param {type: 'string'}\n",
        "ENDPOINT = REGION + \"-aiplatform.googleapis.com\"\n",
        "PARENT = \"projects/{}/locations/{}\".format(PROJECT_ID, REGION)\n",
        "\n",
        "print(\"ENDPOINT: {}\".format(ENDPOINT))\n",
        "print(\"REGION: {}\".format(REGION))\n",
        "print(\"PARENT: {}\".format(PARENT))\n",
        "\n",
        "# If you don't know your project ID, you might be able to get your project ID\n",
        "# using gcloud command by executing the second cell below.\n",
        "if PROJECT_ID == \"\" or PROJECT_ID is None or PROJECT_ID == \"[your-project-id]\":\n",
        "    # Get your GCP project id from gcloud\n",
        "    shell_output = !gcloud config list --format 'value(core.project)' 2>/dev/null\n",
        "    PROJECT_ID = shell_output[0]\n",
        "    print(\"Project ID:\", PROJECT_ID)\n",
        "! gcloud config set project $PROJECT_ID"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8NBduXsEaRKr"
      },
      "source": [
        "### Create the study configuration\n",
        "\n",
        "The following is a sample study configuration, built as a hierarchical python dictionary. It is already filled out. Run the cell to configure the study."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "s-AHfPOASXXW"
      },
      "outputs": [],
      "source": [
        "# Parameter Configuration\n",
        "\n",
        "param_r = {\"parameter_id\": \"r\", \"double_value_spec\": {\"min_value\": 0, \"max_value\": 1}}\n",
        "\n",
        "param_theta = {\n",
        "    \"parameter_id\": \"theta\",\n",
        "    \"double_value_spec\": {\"min_value\": 0, \"max_value\": 1.57},\n",
        "}\n",
        "\n",
        "# Objective Metrics\n",
        "metric_y1 = {\"metric_id\": \"y1\", \"goal\": \"MINIMIZE\"}\n",
        "\n",
        "# Objective Metrics\n",
        "metric_y2 = {\"metric_id\": \"y2\", \"goal\": \"MAXIMIZE\"}\n",
        "\n",
        "# Put it all together in a study configuration\n",
        "study = {\n",
        "    \"display_name\": STUDY_DISPLAY_NAME,\n",
        "    \"study_spec\": {\n",
        "        \"algorithm\": \"RANDOM_SEARCH\",\n",
        "        \"parameters\": [\n",
        "            param_r,\n",
        "            param_theta,\n",
        "        ],\n",
        "        \"metrics\": [metric_y1, metric_y2],\n",
        "    },\n",
        "}\n",
        "\n",
        "print(json.dumps(study, indent=2, sort_keys=True))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uyXG_RKha7Kb"
      },
      "source": [
        "### Create the study\n",
        "\n",
        "Next, create the study, which you will subsequently run to optimize the two objectives."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jgskzqZX0Mkt"
      },
      "outputs": [],
      "source": [
        "vizier_client = aiplatform_v1beta1.VizierServiceClient(\n",
        "    client_options=dict(api_endpoint=ENDPOINT)\n",
        ")\n",
        "study = vizier_client.create_study(parent=PARENT, study=study)\n",
        "STUDY_ID = study.name\n",
        "print(\"STUDY_ID: {}\".format(STUDY_ID))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dKOMBKmtkcJb"
      },
      "source": [
        "### Metric evaluation functions\n",
        "\n",
        "Next, define some functions to evaluate the two objective metrics."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Xnl1uqnyz3Qp"
      },
      "outputs": [],
      "source": [
        "import math\n",
        "\n",
        "\n",
        "# r * sin(theta)\n",
        "def Metric1Evaluation(r, theta):\n",
        "    \"\"\"Evaluate the first metric on the trial.\"\"\"\n",
        "    return r * math.sin(theta)\n",
        "\n",
        "\n",
        "# r * cos(theta)\n",
        "def Metric2Evaluation(r, theta):\n",
        "    \"\"\"Evaluate the second metric on the trial.\"\"\"\n",
        "    return r * math.cos(theta)\n",
        "\n",
        "\n",
        "def CreateMetrics(trial_id, r, theta):\n",
        "    print((\"=========== Start Trial: [{}] =============\").format(trial_id))\n",
        "\n",
        "    # Evaluate both objective metrics for this trial\n",
        "    y1 = Metric1Evaluation(r, theta)\n",
        "    y2 = Metric2Evaluation(r, theta)\n",
        "    print(\n",
        "        \"[r = {}, theta = {}] => y1 = r*sin(theta) = {}, y2 = r*cos(theta) = {}\".format(\n",
        "            r, theta, y1, y2\n",
        "        )\n",
        "    )\n",
        "    metric1 = {\"metric_id\": \"y1\", \"value\": y1}\n",
        "    metric2 = {\"metric_id\": \"y2\", \"value\": y2}\n",
        "\n",
        "    # Return the results for this trial\n",
        "    return [metric1, metric2]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Qzn5lVpRq05U"
      },
      "source": [
        "### Set configuration parameters for running trials\n",
        "\n",
        "__`client_id`__: The identifier of the client that is requesting the suggestion. If multiple SuggestTrialsRequests have the same `client_id`, the service will return the identical suggested trial if the trial is `PENDING`, and provide a new trial if the last suggested trial was completed.\n",
        "\n",
        "__`suggestion_count_per_request`__: The number of suggestions (trials) requested in a single request.\n",
        "\n",
        "__`max_trial_id_to_stop`__: The number of trials to explore before stopping. It is set to 4 to shorten the time to run the code, so don't expect convergence. For convergence, it would likely need to be about 20 (a good rule of thumb is to multiply the total dimensionality by 10).\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5usXaZA5qvUZ"
      },
      "outputs": [],
      "source": [
        "client_id = \"client1\"  # @param {type: 'string'}\n",
        "suggestion_count_per_request = 5  # @param {type: 'integer'}\n",
        "max_trial_id_to_stop = 4  # @param {type: 'integer'}\n",
        "\n",
        "print(\"client_id: {}\".format(client_id))\n",
        "print(\"suggestion_count_per_request: {}\".format(suggestion_count_per_request))\n",
        "print(\"max_trial_id_to_stop: {}\".format(max_trial_id_to_stop))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UnV2SJNskm7V"
      },
      "source": [
        "### Run Vertex Vizier trials\n",
        "\n",
        "Run the trials."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "opmuTntW4-eS"
      },
      "outputs": [],
      "source": [
        "trial_id = 0\n",
        "while int(trial_id) < max_trial_id_to_stop:\n",
        "    suggest_response = vizier_client.suggest_trials(\n",
        "        {\n",
        "            \"parent\": STUDY_ID,\n",
        "            \"suggestion_count\": suggestion_count_per_request,\n",
        "            \"client_id\": client_id,\n",
        "        }\n",
        "    )\n",
        "\n",
        "    for suggested_trial in suggest_response.result().trials:\n",
        "        trial_id = suggested_trial.name.split(\"/\")[-1]\n",
        "        trial = vizier_client.get_trial({\"name\": suggested_trial.name})\n",
        "\n",
        "        if trial.state in [\"COMPLETED\", \"INFEASIBLE\"]:\n",
        "            continue\n",
        "\n",
        "        for param in trial.parameters:\n",
        "            if param.parameter_id == \"r\":\n",
        "                r = param.value\n",
        "            elif param.parameter_id == \"theta\":\n",
        "                theta = param.value\n",
        "        print(\"Trial : r is {}, theta is {}.\".format(r, theta))\n",
        "\n",
        "        vizier_client.add_trial_measurement(\n",
        "            {\n",
        "                \"trial_name\": suggested_trial.name,\n",
        "                \"measurement\": {\n",
        "                    \"metrics\": CreateMetrics(suggested_trial.name, r, theta)\n",
        "                },\n",
        "            }\n",
        "        )\n",
        "\n",
        "        response = vizier_client.complete_trial(\n",
        "            {\"name\": suggested_trial.name, \"trial_infeasible\": False}\n",
        "        )"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "i5ZTqgqBiRsq"
      },
      "source": [
        "### List the optimal solutions\n",
        "\n",
        "list_optimal_trials returns the pareto-optimal Trials for multi-objective Study or the optimal Trials for single-objective Study. In the case, we define mutliple-objective in previeous steps, pareto-optimal trials will be returned."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Or2PL1YxTr33"
      },
      "outputs": [],
      "source": [
        "optimal_trials = vizier_client.list_optimal_trials({\"parent\": STUDY_ID})\n",
        "\n",
        "print(\"optimal_trials: {}\".format(optimal_trials))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KAxfq9Fri2YV"
      },
      "source": [
        "## Cleaning up\n",
        "\n",
        "To clean up all Google Cloud resources used in this project, you can [delete the Google Cloud\n",
        "project](https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects) you used for the tutorial. You can also manually delete resources that you created by running the following code."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zQlLDfvlzYde"
      },
      "outputs": [],
      "source": [
        "vizier_client.delete_study({\"name\": STUDY_ID})"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "gapic-vizier-multi-objective-optimization.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
